package fnord.network.server;

import java.io.*;
import java.net.*;
import java.util.*;
import java.util.concurrent.*;

import fnord.exception.BadPortException;
import fnord.illuminati.order.Order;
import fnord.network.NetworkConstants;
import fnord.network.OrderType;

public class ServerCommunicator {
	
	private ServerSocket _serverSocket;
	private Map<Player, ClientListenerThread> _clients;
	private Map<ClientListenerThread, BlockingQueue<Order>> _orders;
	private Map<ClientListenerThread, ObjectOutputStream> _streams;
	private Server _server;
	
	public ServerCommunicator(Server server, int port) throws BadPortException {
		_server = server;
		try {
			_serverSocket = new ServerSocket(port);
		} catch (IOException e) {
			throw new BadPortException(port);
		}
		_clients = new HashMap<Player, ClientListenerThread>();
		_orders = new HashMap<ClientListenerThread, BlockingQueue<Order>>();
		_streams = new HashMap<ClientListenerThread, ObjectOutputStream>();
	}
	
	/**
	 * This method waits on clients, and starts the game once the requisite number of clients
	 * have connected
	 * @param numPlayers
	 * @param ref
	 * @return
	 * @throws IOException
	 */
	public List<Player> getClients(int numPlayers, Referee ref) throws IOException {
		List<Player> players = new ArrayList<Player>();
		for(int i = 0; i<numPlayers && !_serverSocket.isClosed(); i++) {
			Socket client;
			Player player;
			client = _serverSocket.accept();
			player = new Player(ref.getNextIlluminati());
			ClientListenerThread clt = new ClientListenerThread(client, _server);
			_streams.put(clt, new ObjectOutputStream(client.getOutputStream()));
			_streams.get(clt).writeObject(player);
			_streams.get(clt).flush();
			_clients.put(player, clt);
			_orders.put(clt, new LinkedBlockingQueue<Order>());
			if(players.size() > 0) players.get(players.size()-1).setNextPlayer(player);
			if(i == numPlayers-1 && numPlayers > 1) player.setNextPlayer(players.get(0));
			if(numPlayers == 1) player.setNextPlayer(player);
			players.add(player);
			ref.addPlayer(player);
			clt.start();
		}
		Set<Player> playerSet = new HashSet<Player>();
		playerSet.addAll(players);
		ref.setPlayers(playerSet);
		sendData(OrderType.GAME_STATE, ref.getBoard(), null);
		return players;
	}
	
	public void putOrder(ClientListenerThread putting, Order toPut) {
		try {
			_orders.get(putting).put(toPut);
		} catch (InterruptedException e) {
			System.err.println("a-here it goes a-here it");
			System.err.println("AAAARRRRNOOLD");
		}
	}
	
	public Order getLastOrder(Player fromPlayer) {		
		try {
			return _orders.get(_clients.get(fromPlayer)).poll(NetworkConstants.timeout, TimeUnit.SECONDS);
		} catch (InterruptedException e) {
			System.err.println("AWWWWWWW HERE IT GOES");
			System.err.println("KEENAN");
			System.err.println("AND KEL!!!");
		}
		return null;
	}
	
	/**
	 * This method takes in an order type, an object to send over the network, and a player to send to
	 * If the order should be sent to all clients, the Player argument is ignored.
	 */
	public void sendData(OrderType type, Serializable toSend, Player sendTo) {
		if(type.sentByServer()) {
			if(type.toAll()) {
				for(ClientListenerThread c : _clients.values()) {
					try {
						ObjectOutputStream oos = _streams.get(c);
						oos.reset();//to make sure we don't accidentally send a cached value...
						oos.writeObject(type.getString());
						oos.flush();
						oos.reset();
						oos.writeObject(toSend);
						oos.flush();
					} catch (IOException e) {
						//for(Player p : _clients.keySet())
							//_server.sendErrorToPlayer(p, "A Player Has Disconnected");
					}
						
				}
			}
			else {
				try {
					ObjectOutputStream oos = _streams.get(_clients.get(sendTo));//new ObjectOutputStream(_clients.get(sendTo).getSocket().getOutputStream());
					oos.reset();//to make sure we don't accidentally send a cached value...
					oos.writeObject(type.getString());
					oos.flush();
					oos.writeObject(toSend);
					oos.flush();
				} catch (IOException e) {
					//_server.sendErrorToPlayer(sendTo, "A Player Has Disconnected");
				}
			}
		}
	}

	public boolean isOpen() {
		return !_serverSocket.isClosed();
	}

	public int getPort() {
		return _serverSocket.getLocalPort();
	}

	public Player closePlayer(ClientListenerThread clientListenerThread) {
		try {
			_streams.get(clientListenerThread).close();
		} catch (IOException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		_streams.remove(clientListenerThread);
		_orders.remove(clientListenerThread);
		for(Player p : _clients.keySet()) {
			if(_clients.get(p) == clientListenerThread) {
				_clients.remove(p);
				return p;
			}
		}
		return null;
	}
	
	
}
